/*
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#define PLUGIN_NAME		"Defibfix"
#define PLUGIN_AUTHOR		"Blaize"
#define PLUGIN_DESC		"track the dead player and simulate defib"
#define PLUGIN_VERSION		"0.1"
#define PLUGIN_FILENAME		"defibfix"

#define DEAD_MODEL "models/props_junk/petfoodbag01.mdl"
#define DEFIB_SOUND "weapons/defibrillator/defibrillator_use.wav"
#define DEFIB_SUCCESS "ui/bigreward.wav"
#define NOBODY -1

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <smlib>
#include <l4d_stocks>

#include "macros.sp"
#include "helpers.sp"


new player_defib_array[MAXCLIENTS] = { -1, ... };
new Handle:hRoundRespawn = INVALID_HANDLE;
new Handle:hGameConf = INVALID_HANDLE;
new Handle:cvar_revive_distance = INVALID_HANDLE;
new Handle:cvar_revive_duration = INVALID_HANDLE;

new Handle:timer_defib[MAXCLIENTS] = {INVALID_HANDLE, ...};
new on_defib[MAXCLIENTS] = { NOBODY, ... };

public OnPluginStart()
{
	hGameConf = LoadGameConfigFile("l4d2defibfix");
	StartPrepSDKCall(SDKCall_Player);
	PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "RoundRespawn");
	hRoundRespawn = EndPrepSDKCall();
	if (hRoundRespawn == INVALID_HANDLE) SetFailState("L4D_SM_Respawn: RoundRespawn Signature broken");

	cvar_revive_distance = FindConVar("sb_revive_friend_distance");
	cvar_revive_duration = FindConVar("defibrillator_use_duration");

	HookEvent("round_start", event_round_start);
	HookEvent("round_end", event_round_end);
	HookEvent("player_death", event_player_death);
	HookEvent("weapon_fire_on_empty", event_weapon_fire_on_empty);
	HookEvent("player_bot_replace", Event_PlayerBotReplace);
	HookEvent("bot_player_replace", Event_BotPlayerReplace);

	/*event can interrupt */
	/*smoker*/
	HookEvent("tongue_grab", event_inf_grab);
	/*hunter*/
	HookEvent("lunge_pounce", event_inf_grab);
	/*jockey*/
	HookEvent("jockey_ride", event_inf_grab);
	/*charger*/
	HookEvent("charger_carry_start", event_inf_grab);

}

public OnMapStart()
{
	PrecacheModel(DEAD_MODEL);
	PrecacheSound(DEFIB_SOUND);
	PrecacheSound(DEFIB_SUCCESS);
}


/*require sdkhooks*/
public OnEntityCreated(entity, const String:classname[])
{
	if(!IsValidEdict(entity) || !IsValidEntity(entity))
		return;	

	decl String:name[32];
	GetEntityClassname(entity, name, sizeof(name));
	if(strcmp(name, "survivor_death_model") == 0)
	{
		SDKHook(entity, SDKHook_Spawn, OnEntitySpawned)
	}

}

public Action:OnEntitySpawned(entity)
{
	AcceptEntityInput(entity, "Kill");
	return Plugin_Handled;
}

/* call this at the start of the round, nobody is dead yet */
clear_player_defib_array()
{
	for(new i=0; i < MAXCLIENTS; i++)
	{
		player_defib_array[i] = -1;
		on_defib[i] = NOBODY;
	}
}

remove_all_glow()
{
	for(new i=0; i < MAXCLIENTS; i++)
	{
		if(player_defib_array[i] != -1)
		{
			L4D2_RemoveEntityGlow(player_defib_array[i]);
		}
	}
}
/* this function must be called when a survivor die */
store_player_defib_array(client, dogbag)
{
	if(IsValidSurvivor(client))
		player_defib_array[client] = dogbag;
}

/* this fonction must be called when a survivor is defib */
remove_player_defib_array(client)
{
	if(IsValidSurvivor(client))
		player_defib_array[client] = -1;
}

get_player_defib(dogbag)
{

	for(new i=0; i < MAXCLIENTS; i++)
	{
		if(player_defib_array[i] == dogbag)
		{
			return i;
		}
	}

	return -1;
}

swap_dogbag(client, bot)
{
	if(IsValidSurvivor(client) && IsValidSurvivor(bot))
	{
		new doggy = player_defib_array[client];
		player_defib_array[client] = player_defib_array[bot];
		player_defib_array[bot] = doggy;
		
		sub_debug("Swapped %N(%d) <-> %N(%d)", client, client, bot, bot)
	}
}


/* return entity dogbag if he can defib, or -1 */
defibfix_player_try_defib(client)
{

	new target = GetClientAimTarget(client, false);

	if(!IsValidEntity(target) || !IsValidEdict(target))
		return;

	new client_to_defib = get_player_defib(target);

	if(client_to_defib == -1)
		return;

	if(get_distance_client_entity(client, target) > GetConVarInt(cvar_revive_distance))
		return;

	/*check if player have a defib on him */
	new defib = GetPlayerWeaponSlot(client, 3); //3 is medikit / defib
	if(defib == -1)
	{
		PrintToChat(client, "[Defib] You don't have defibrillator");
		return;
	}

	decl String:wep_name[32];
	GetEntityClassname(defib, wep_name, sizeof(wep_name));

	if(strcmp(wep_name, "weapon_defibrillator") != 0)
	{
		PrintToChat(client, "[Defib] You don't have defibrillator");
		return;
	}
	
	//OK, go defib. Choose a valid target	
	if(IsValidSurvivorDead(client_to_defib)) //if player had takeover on other bot, or switched team
	{
		if (client_to_defib == client)
			return;
		
		defibfix_player_defib(client, target, client_to_defib);
	}
	else
	{
		remove_player_defib_array(client_to_defib); // not dead
		//defib someone else
		FOR_EACH_DEAD_SURVIVOR(dead)
		{
			store_player_defib_array(dead, target);
			defibfix_player_defib(client, target, dead);
			return;
		}
		sub_debug("nobody to defib !!");
	}

}

defibfix_player_defib(client, target, client_to_defib)
{

	if(on_defib[client] != NOBODY)
		return;

	on_defib[client] = client_to_defib;

	SetEntPropEnt(client_to_defib, Prop_Send, "m_reviveOwner", client);
	SetEntPropEnt(client, Prop_Send, "m_reviveTarget", client_to_defib);
	
	PrintHintText(client, "Target : %N", client_to_defib);

	Load_Unload_ProgressBar(client, GetConVarFloat(cvar_revive_duration));
	
//	new Handle:event = CreateEvent("defibrillator_begin");
//	SetEventInt(event, "userid", client);
//	SetEventInt(event, "subject", client_to_defib);
//	FireEvent(event);

	//play defib sound
	EmitSoundToAll(DEFIB_SOUND, client);

	new Handle:data;

	timer_defib[client] = CreateDataTimer(GetConVarFloat(cvar_revive_duration), timer_defib_check, data, TIMER_FLAG_NO_MAPCHANGE);

	WritePackCell(data, client);
	WritePackCell(data, target);
	WritePackCell(data, client_to_defib);

}

defibfix_interrup_defib(client)
{

	if(on_defib[client] == NOBODY)
		return;

	new client_to_defib = on_defib[client];

	on_defib[client] = NOBODY;

	Load_Unload_ProgressBar(client, CANCEL_BAR);	

	SetEntPropEnt(client_to_defib, Prop_Send, "m_reviveOwner", -1);
	SetEntPropEnt(client, Prop_Send, "m_reviveTarget", -1);
	
	/*trigger defib event */
//	new Handle:event = CreateEvent("defibrillator_interrupted");
//	SetEventInt(event, "userid", client);
//	SetEventInt(event, "subject", client_to_defib);
//	FireEvent(event);

	if(timer_defib[client] != INVALID_HANDLE)
	{
		KillTimer(timer_defib[client]);
		timer_defib[client] = INVALID_HANDLE;
	}
}

public Action:timer_defib_check(Handle:timer, Handle:data)
{

	ResetPack(data);
	new client = ReadPackCell(data);
	new target = ReadPackCell(data);
	new client_to_defib = ReadPackCell(data);

	if(on_defib[client] == NOBODY)
		return;

	if(!IsValidSurvivorAlive(client))
	{
		defibfix_interrup_defib(client);
		return;
	}

	if(!IsValidSurvivorDead(client_to_defib))
	{
		defibfix_interrup_defib(client);
		return;
	}

	if(get_distance_client_entity(client, target) > GetConVarInt(cvar_revive_distance))
	{
		defibfix_interrup_defib(client);
		return;
	}

	//finnaly defib the client if all is OK
	defibfix_defib_done(client, target, client_to_defib);

}

defibfix_defib_done(client, target, client_to_defib)
{
	if(on_defib[client] == NOBODY)
		return;

	on_defib[client] = NOBODY;

	SetEntPropEnt(client_to_defib, Prop_Send, "m_reviveOwner", -1);
	SetEntPropEnt(client, Prop_Send, "m_reviveTarget", -1);

	/*teleport bot at dogbag position */
	decl Float:pos[3];
	GetEntityAbsOrigin(target, pos);
	
	SDKCall(hRoundRespawn, client_to_defib);

	TeleportEntity(client_to_defib, pos, NULL_VECTOR, NULL_VECTOR);

	/*remove the dogbag from the map and array*/
	remove_player_defib_array(client_to_defib);

	//remove dogbag
	AcceptEntityInput(target, "Kill");

	PrintToChatAllExclude(client, client_to_defib, "\x04%N\x01 used defibrillator on \x04%N\x01", client, client_to_defib);
	PrintToChatOnly(client, client_to_defib, "\x05%N\x01 used defibrillator on \x05%N\x01", client, client_to_defib);

	//play success defib sound
	EmitSoundToClient(client, DEFIB_SUCCESS);
	EmitSoundToClient(client_to_defib, DEFIB_SUCCESS);

	/*increment the defib count */
//	new actual = GetEntProp(client, Prop_Data, "m_missionDefibrillatorsUsed");
//	actual++;
//	SetEntProp(client, Prop_Data, "m_missionDefibrillatorsUsed", actual);

	/*remove defibrillator */
	new defib = GetPlayerWeaponSlot(client, 3); //3 is medikit / defib
	if(defib != -1)
	{
		RemoveEdict(defib);
	}

	/*trigger defib event */
//	new Handle:event = CreateEvent("defibrillator_used");
//	SetEventInt(event, "userid", client);
//	SetEventInt(event, "subject", client_to_defib);
//	FireEvent(event);

}

defibfix_defib_show(client)
{
	if(!IsValidHumanClient(client))
		return;
	

	new target = GetClientAimTarget(client, false);

	if(!IsValidEntity(target) || !IsValidEdict(target))
		return;

	new client_to_defib = get_player_defib(target);

	if(client_to_defib == -1)
		return;

	if(get_distance_client_entity(client, target) > GetConVarInt(cvar_revive_distance))
		return;

	/*client focus a doggybag, show him who are behind */
	PrintHintText(client, "RIP %N : Use defibrillator on it!", client_to_defib);
	
}
// ------------------------------------------------------------------------
// Event player death
// ------------------------------------------------------------------------
public Action:event_player_death(Handle:event, const String:name[], bool:dontBroadcast)
{
	
	new client = GetClientOfUserId(GetEventInt(event, "userid"));

	if(IsValidSurvivor(client))
	{
		/*create a dogbag when player dead */
		decl Float:pos[3];
		GetClientAbsOrigin(client, pos);
		decl Float:angle[3];
		GetClientEyeAngles(client, angle);
		angle[0] = -90.0;
		angle[2] = 90.0;
		new prop = create_entity(ENTITY_DYNAMIC, DEAD_MODEL, pos, angle, false);
		store_player_defib_array(client, prop);
		sub_debug("Store %N : %d", client, prop);

		//add glow
		decl color[3];
		color[0] = 255;
		color[1] = 190;
		color[2] = 120;
		L4D2_SetEntityGlow(prop, L4D2Glow_Constant, 2048, 20, color, false);	
	}
}


// ------------------------------------------------------------------------
// Event round start
// ------------------------------------------------------------------------
public Action:event_round_start(Handle:event, const String:name[], bool:dontBroadcast)
{
	clear_player_defib_array();
}

public Action:event_round_end(Handle:event, const String:name[], bool:dontBroadcast)
{
	remove_all_glow();
}


public Action:event_weapon_fire_on_empty(Handle:event, const String:name[], bool:dontBroadcast)
{

	new client = GetClientOfUserId(GetEventInt(event, "userid"));
	
	if(!IsValidHumanSurvivorAlive(client))
		return;

	if(on_defib[client] != NOBODY)
		return;

	
	decl String:wep[32];
	GetEventString(event, "weapon", wep, sizeof(wep));

	if(strcmp(wep, "defibrillator") == 0)
	{
		defibfix_player_try_defib(client);
	}
	
}


public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon)
{
	if(IsValidHumanClient(client) && buttons & IN_USE)
	{
		defibfix_defib_show(client);
	}

	if(!IsValidHumanSurvivorAlive(client))
		return;

	if(on_defib[client] == NOBODY)
		return;

	if(buttons & IN_ATTACK2 )
	{
		defibfix_interrup_defib(client);
	}

	else if(buttons & IN_JUMP)
	{
		defibfix_interrup_defib(client);
	}

	else if(buttons & IN_FORWARD )
	{
		defibfix_interrup_defib(client);
	}
	
	else if(buttons & IN_BACK )
	{
		defibfix_interrup_defib(client);
	}

	else if(buttons & IN_LEFT )
	{
		defibfix_interrup_defib(client);
	}

	else if(buttons & IN_RIGHT )
	{
		defibfix_interrup_defib(client);
	}

	else if(buttons & IN_USE )
	{
		defibfix_interrup_defib(client);
	}

	else if(buttons & IN_MOVERIGHT )
	{
		defibfix_interrup_defib(client);
	}

	else if(buttons & IN_MOVELEFT )
	{
		defibfix_interrup_defib(client);
	}
}

public  Event_PlayerBotReplace(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client = GetClientOfUserId(GetEventInt(event, "player"));
	new bot =  GetClientOfUserId(GetEventInt(event, "bot"));
	
	swap_dogbag(client, bot);
}

public  Event_BotPlayerReplace(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client = GetClientOfUserId(GetEventInt(event, "player"));
	new bot =  GetClientOfUserId(GetEventInt(event, "bot"));

	swap_dogbag(client, bot);
}

public Action:event_inf_grab(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client = GetClientOfUserId(GetEventInt(event, "victim"));

	if(IsValidSurvivor(client))
	{
		defibfix_interrup_defib(client);
	}
}